Als nächstes wollen wir uns anschauen, auf welche Probleme man gegebenenfalls stößt,
wenn man Threads verwendet und welche Mittel man zur Hand hat, um diese Probleme auch wieder
zu lösen.
Dazu schauen wir uns zunächst einmal folgendes Beispiel an.
Hier wollen wir alle Werte eines zweidimensionalen Arrays aufsummieren.
Um diesen Vorgang zu beschleunigen, starten wir für jede Zeile des Arrays einen eigenen
Thread, der die jeweilige Zeile lokal aufsummiert und das Ergebnis der Zeile auf eine globale
Variable addiert.
Dies ist in der SumRow-Funktion implementiert.
Die Frage, die sich stellt, ist, funktioniert das so?
Und die Antwort ist, wenig überraschend, nein, es funktioniert nicht.
Das Problem ist hier in der hervorgebenden Zeile.
Jeder der Threads versucht, sobald er das Ergebnis seiner eigenen Zeile hat, dieses auf
eine einzelne globale Variable aufzuaddieren.
Was nun hier als ein einzelnes Statement aufgezeichnet ist, sind in Wahrheit tatsächlich mehrere
Instruktionen, die ausgeführt werden.
Hier wird der plus-is-gleich-Operator eingesetzt, der tatsächlich zwei verschiedene Schritte
sind.
Zum einen wird der aktuelle Wert von Sum geladen.
Anschließend werden Sum und die Variable localSum zusammengerechnet und in einem dritten
Schritt wird das Ergebnis wieder zurück in die Variable Sum geschrieben.
Nachdem wir nun aber alles parallel machen wollen, kann es sein, dass zwei verschiedene
Threads genau diese eine Code-Zeile ausführen und sich dabei in einem der drei Schritte
befinden.
Wenn wir nun annehmen, dass zwei Threads gleichzeitig die Zeile ausführen wollen, können wir uns
vorstellen, dass beide nun im ersten Schritt den aktuellen Wert Sum laden.
In dem zweiten und dritten Schritt wird das Ergebnis aufaddiert und beide versuchen ihr
jeweiliges Ergebnis wieder zurückzuschreiben.
Was nun aber passiert ist, dass die Threads unabhängig voneinander den Wert Sum gelesen
haben und beide auf demselben Summenwert ihr eigenes Ergebnis aufaddieren und dieses zurückschreiben.
Dabei bleibt nur noch das zuletzt geschriebene Ergebnis erhalten und zwar ohne die Änderung,
die der parallel laufende Thread machen wollte.
Es gilt also mindestens ein Ergebnis verloren.
Dies ist etwas, was wir nicht akzeptieren können.
Deshalb brauchen wir nun eine Möglichkeit, um solches Verhalten zu verhindern.
Und das Werkzeug, das wir in SP an die Hand bekommen, sind sogenannte Semaphore.
Semaphore können zur Koordinierung von Threads verwendet werden.
POSIX bietet bereits eine Schnittstelle für Semaphoren an.
Diese ist aber sehr komplex, da sie auch verwendet werden kann, um verschiedene Prozesse, die
voneinander isoliert laufen, zu koordinieren.
Anstelle der POSIX-Variante verwenden wir eine eigene Implementierung, die sich im Wesentlichen
auf die beiden Operationen P und V beschränkt.
Ein Semaphore ist dabei mehr oder weniger eine Zählervariable und mit der P-Operation
kann die Zählervariable dekrementiert werden, während eine V-Operation sie wieder incrementiert.
Der Trick an der Sache ist jedoch, dass der Aufrufer einer P-Operation blockiert, falls
der Wert einer Zählervariable kleiner als Null werden würde.
Gleichermaßen werden durch den Aufruf der V-Funktion gegebenenfalls blockierte Threads
wieder deblockiert.
Details dazu sind in den Vorlesungsfolien zu finden.
Semaphoren sind dabei sehr vielfältig einsetzbar.
Zugänglich über
Offener Zugang
Dauer
00:09:49 Min
Aufnahmedatum
2020-07-06
Hochgeladen am
2020-07-06 12:26:32
Sprache
de-DE